using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using CatLib;
using CatLib.Container;
using Cysharp.Threading.Tasks;
using GameEntry;
using GameFramework.Business;
using GameFramework.Core;
using GameFramework.Extensions;
using GameFramework.Procedure;
using GameFramework.Update;
using UnityEngine;
using Application = CatLib.Application;

namespace GameFramework.App
{
    public abstract class AGameApp : MonoBehaviour, IGameApp, IBootstrap, IUpdate
    {
        private Application m_Application;
        private IGameLoader m_GameLoader;

        private readonly List<IViewController> m_ViewControllers = new();
        private readonly List<IUpdate> m_Updates = new();
        
        public void Init(IGameLoader gameLoader)
        {
            m_GameLoader = gameLoader;
            var bootstraps = GetAppBootstraps(m_GameLoader.CurrentLogicAssembles);
            bootstraps.Insert(0, this);

            CatLib.App.That = m_Application = new Application();
            CatLib.App.OnNewApplication += OnNewApplication;
            
            m_Application.GetDispatcher().AddListener(ApplicationEvents.OnStartCompleted, OnStartCompleted);
            m_Application.OnAfterResolving<IInitialize>(inst => inst.Initialize());
            m_Application.Bootstrap(bootstraps.ToArray());
            m_Application.Init();
        }

        private void OnNewApplication(IApplication application)
        {
            if (application == m_Application)
                return;
            
            DestroyImmediate(gameObject);
        }

        private void Awake()
        {
            DontDestroyOnLoad(gameObject);
        }

        private void OnDestroy()
        {
            for (var index = m_ViewControllers.Count - 1; index >= 0; --index)
            {
                var controller = m_ViewControllers[index];
                
                try
                {
                    controller.Dispose();
                }
                catch (Exception e)
                {
                    Log.Exception(e);
                }
            }
            
            CatLib.App.OnNewApplication -= OnNewApplication;
            m_Application.Terminate();
            m_Application = null;
        }

        public void Reboot()
        {
            var gameLoader = m_GameLoader;

            DestroyImmediate(gameObject);

            gameLoader.LoadGameLogicAssemblies().Forget();
        }

        public void Quit(int errorCode = 0)
        {
            DestroyImmediate(gameObject);
            UnityEngine.Application.Quit(errorCode);
        }

        public void Bootstrap()
        {
            CatLib.App.Instance<IGameApp>(this);
            CatLib.App.Instance<IGameLoader>(m_GameLoader);
        }

        private static List<IBootstrap> GetAppBootstraps(IEnumerable<Assembly> assemblies)
        {
            var bootstrapType = typeof(IBootstrap);
            var bootstraps = (from assembly in assemblies from type in assembly.GetTypes() where type.IsClass && !type.IsAbstract where bootstrapType.IsAssignableFrom(type) let attribute = type.GetCustomAttribute<AppBootstrapAttribute>() where attribute != null let priority = attribute.Priority let bootstrap = (IBootstrap)Activator.CreateInstance(type) select (bootstrap, priority)).ToList();

            int Comparison((IBootstrap bootstrap, int priority) x, (IBootstrap bootstrap, int priority) y)
            {
                return y.priority.CompareTo(x.priority);
            }

            bootstraps.Sort(Comparison);
            return bootstraps.Select(item => item.bootstrap).ToList();
        }

        private void OnStartCompleted(object sender, EventArgs _)
        {
            OnAddControllers(m_ViewControllers);
                
            foreach (var controller in m_ViewControllers)
            {
                try
                {
                    m_Application.Inject(controller);
                    controller.Initialize();
                    
                    // ReSharper disable once SuspiciousTypeConversion.Global
                    if (controller is IUpdate update)
                        m_Updates.Add(update);
                }
                catch (Exception e)
                {
                    Log.Exception(e);
                }
            }
            
            if (m_Updates.Count > 0)
                UpdateService.That.RegisterUpdate(this);
            
            ProcedureService.That.Run(m_GameLoader.CurrentLogicAssembles);
        }

        public void OnUpdate(float elapseSeconds, float realElapseSeconds)
        {
            if (m_Updates.Count == 0)
                return;

            foreach (var update in m_Updates)
            {
                try
                {
                    update.OnUpdate(elapseSeconds, realElapseSeconds);
                }
                catch (Exception e)
                {
                    Log.Exception(e);
                }
            }
        }

        /// <summary>
        /// 派生类在该方法中添加所需的视图控制器
        /// </summary>
        protected virtual void OnAddControllers(List<IViewController> controllers)
        {
        }
    }
}